package ar.posti.framework.query.core;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import ar.posti.framework.query.core.filter.criteria.Equals;
import ar.posti.framework.query.hibernate.filter.From;
import ar.posti.framework.query.hibernate.filter.Select;
import ar.posti.framework.query.hibernate.filter.Where;

public class DynamicQueryTest {
	public class MockQueryExecutor implements QueryExecutor<MockQuery> {
		private MockQuery query;
		private Object returnedObject = new Object();
		private List returnedSublist = Arrays.asList(new Object[] { new Object() }); 
		private List returnedList = Arrays.asList(new Object[] { new Object() }); 
		private int returnedInt = -123456;

		public int queryForInt(MockQuery query) {
			this.query = query;
			return returnedInt;
		}

		public List queryForList(MockQuery query) {
			this.query = query;
			return returnedList;
		}

		public List queryForList(MockQuery query, Integer firstElement, Integer lastElement) {
			this.query = query;
			return returnedSublist;
		}

		public Object queryForObject(MockQuery query) {
			this.query = query;
			return returnedObject;
		}

		public Iterator queryForIterator(MockQuery query) {
			// TODO Auto-generated method stub
			return null;
		}

		public Iterator queryForIterator(MockQuery query, Integer firstElement, Integer lastElement) {
			// TODO Auto-generated method stub
			return null;
		}

		public void assertQueryForObject(MockQuery expectedQuery, Object returnedValue) {
			expectedQuery.assertEquals(query);
			Assert.assertEquals(returnedObject, returnedValue);
		}

		public void assertQueryForList(MockQuery expectedQuery, Object returnedValue) {
			expectedQuery.assertEquals(query);
			Assert.assertEquals(returnedList, returnedValue);
		}

		public void assertQueryForSublist(MockQuery expectedQuery, Object returnedValue) {
			expectedQuery.assertEquals(query);
			Assert.assertEquals(returnedSublist, returnedValue);
		}

		public void assertQueryForInt(MockQuery expectedQuery, int returnedValue) {
			expectedQuery.assertEquals(query);
			Assert.assertEquals(returnedInt, returnedValue);
		}
	}

	public class MockQuery {
		private Map<String, Object> variables;
		private String queryString;

		public MockQuery(String queryString, Map<String, Object> variables) {
			this.queryString = queryString;
			this.variables = variables;
		}

		public void assertEquals(MockQuery expected) {
			Assert.assertEquals(queryString, expected.queryString);
			Assert.assertEquals(variables, expected.variables);
		}
	}

	public class MockQueryAssembler extends AbstractQueryAssembler<MockQuery> {
		@Override
		public MockQuery assemble() {
			StringBuilder queryBuffer = new StringBuilder();
			Map<String, Object> variables = new TreeMap<String, Object>();
			super.doAssemble(queryBuffer, variables);
			return new MockQuery(queryBuffer.toString(), variables);
		}
	}

	public class MockQueryAssemblerFactory implements QueryAssemblerFactory {
		public MockQueryAssembler createAssembler() {
			return new MockQueryAssembler();
		}
	}

	private DynamicQuery<?> query;
	private MockQueryExecutor mockQueryExecutor;

	@Before
	public void setUp() {
		Filter[] filters = new Filter[] {
				new Select("this"),	
				new From(Object.class, "this").
					leftJoinFetch("this.defaultGraphicObject"),
				new Where
					(new Equals("this.id", ":string")).and
					(new Equals("this.toolType", ":integer")).and
					(new Equals("this.length", ":int")).and
					(new Equals("this.creativityCategory", ":category")).and
					(new Equals("this.height", ":height")).and
					(new Equals("this.countrySite", ":country"))
			};

		mockQueryExecutor = new MockQueryExecutor();
		query = new DynamicQuery();
		query.compose(Arrays.asList(filters));
		query.setAssemblerFactory(new MockQueryAssemblerFactory());
		query.setQueryExecutor(mockQueryExecutor);
	}
	
	@Test public void testUniqueResultNoVariables() {
		String expectedString = "select this from java.lang.Object this left join fetch this.defaultGraphicObject";
		TreeMap<String, Object> expectedVariables = new TreeMap<String, Object>();
		MockQuery expectedQuery = new MockQuery(expectedString, expectedVariables);

		Object returnedValue = query.uniqueResult();
		
		mockQueryExecutor.assertQueryForObject(expectedQuery, returnedValue);
	}

	@Test public void testUniqueResultOneVariable() {
		String expectedString = "select this from java.lang.Object this left join fetch this.defaultGraphicObject where (this.creativityCategory = :category)";
		TreeMap<String, Object> expectedVariables = new TreeMap<String, Object>();
		expectedVariables.put("category", new Object());
		MockQuery expectedQuery = new MockQuery(expectedString, expectedVariables);

		this.bindVariables(expectedVariables);
		Object returnedValue = query.uniqueResult();
		
		mockQueryExecutor.assertQueryForObject(expectedQuery, returnedValue);
	}

	@Test public void testUniqueResultTwoVariables() {
		String expectedString = "select this from java.lang.Object this left join fetch this.defaultGraphicObject where (this.id = :string and this.creativityCategory = :category)";
		TreeMap<String, Object> expectedVariables = new TreeMap<String, Object>();
		expectedVariables.put("category", new Object());
		expectedVariables.put("string", new Object());
		MockQuery expectedQuery = new MockQuery(expectedString, expectedVariables);

		this.bindVariables(expectedVariables);
		Object returnedValue = query.uniqueResult();

		mockQueryExecutor.assertQueryForObject(expectedQuery, returnedValue);
	}

	@Test public void test() {
		Assert.fail("Other tests not yet implemented");
	}

	private void bindVariables(TreeMap<String, Object> expectedVariables) {
		for (String key : expectedVariables.keySet()) {
			query.bindVariable(key, expectedVariables.get(key));
		}
	}
}
